package jmpo.col;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * В Java стандартная библиотека обладает огромными возможностями, 
 * в том числе по обработке наборов объектов. JCF - Java Collection 
 * Framework - позволяет стандартизировать работу с наборами объектов 
 * для всех проектов, использующих Java.
 */
/**
 * Основу составляют два интерфейса: 
 * Iterable и Iterator.
 * Интерфейс Iterator явяетя абстракцией последовательности и
 * предоставляет два важнейших метода:
 *     next() - забирает следующий интерфейс из последовательности
 *     hasNext() - проверяет наличие интерфейса в последовательности
 * Начиная с Java 6 этот интерфейс является параметризованным и на этапе 
 * компиляции происходит проверка типов.
 */
/**
 * Напишем простой класс, который создаёт последовательность из 
 * строк, содержащих только одну букву, но увеличивающейся длины.
 */
final class StringStretchIterator implements Iterator<String> {
    @Override
    public boolean hasNext() {
        return maxLength != currLenght;
    }

    @Override
    public String next() {
        if (maxLength == currLenght) 
            throw new NoSuchElementException("The StringStretchIterator has reached the end");
        sb.append(symbol);
        ++currLenght;
        return sb.toString();
            
    }

    public StringStretchIterator(char symbol, int maxLength) {
        this.symbol = symbol;
        this.maxLength = maxLength;
        this.sb = new StringBuilder();
        this.currLenght = 0;
    }

    private int currLenght;
    private final int maxLength;
    private final StringBuilder sb;
    private final char symbol;

    public static void test() {
        var it = new StringStretchIterator('*', 10);
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

/**
 * Интерфейс Iterator также содержит некоторые методы, которые реализованы
 * по умолчанию:
 *     void remove() - удаляет текущий элемент, по умолчанию выбрасывает исключение
 *     UnsupportedOperationException (т.е. из данной колллекции нельзя
 *     удалить элемент)
 * 
 *     forEachRemaining(Consumer<? super E> action) - это специальный метод для 
 *     Java 8 Stream API, он позволяет для всех ещё содержащихся в последовательности
 *     элементов выполнить указанную операцию
 */

/**
 * Интерфейс Iterable представляет собой абстракцию перечислимого контейнера,
 * от которого можно брать итераторы, чтобы его обходить.
 * Данный интерфейс требует реализовать только один метод:
 *     Iterator<T> iterator() - метод возвращает новый объект итератор над заданной
 *     последовательностью.
 * Если класс реализует этот интерфейс, то он может использоваться в специальном 
 * варианте цикла for:
 *     for (T x : Iterable<T> i)
 */
final class StringStretch implements Iterable<String> {
    public StringStretch(char symbol, int maxLength) {
        this.symbol = symbol;
        this.maxLength = maxLength;
    }

    @Override
    public Iterator<String> iterator() {
        return this.new StretchIterator();
    }

    /**
     * InnerClass имеет доступ к данным своего OuterClass
     */
    private final class StretchIterator implements Iterator<String> {
        @Override
        public boolean hasNext() {
            return currLength != maxLength;
        }

        @Override
        public String next() {
            if (currLength == maxLength)
                throw new NoSuchElementException("The StretchIterator has reached the end");
            ++currLength;
            sb.append(symbol);
            return sb.toString();
        }

        StretchIterator() {
            this.currLength = 0;
            this.sb = new StringBuilder();
        }

        private int currLength;
        private final StringBuilder sb;
    }
    
    private final int maxLength;
    private final char symbol;

    public static void test() {
        Iterable<String> sequence = new StringStretch('$', 10);
        for (var s : sequence)
            System.out.println(s);
    }
}

/**
 * Классы Iterable и Iterator реализуют произвольные последовательности,
 * даже бесконечные или с неизвестным заранее количеством итераций.
 */
final class SyracuseSequence implements Iterable<Long> {
    public SyracuseSequence(long initial_value) {
        this.initial_value = initial_value;
    }

    @Override
    public Iterator<Long> iterator() {
        return this.new SyracuseIterator(initial_value);
    }

    private final class SyracuseIterator implements Iterator<Long> {
        @Override
        public boolean hasNext() {
            return current != 1l;
        }

        @Override
        public Long next() {
            if (0 == current % 2)
                return current = current / 2;
            return current = current * 3 + 1;
        }

        SyracuseIterator(long current) {
            this.current = current;
        }

        private Long current;
    }

    private final long initial_value; 
    
    public static void test() {
        Iterable<Long> sequence = new SyracuseSequence(1023);
        for (var x : sequence)
            System.out.println(x);
    }
}

/**
 * Более богатым является интерфейс Collection.
 * Этот интерфейс представляет абстракцию некоторого контейнера,
 * некоторого конечного множества.
 * Важнейшим отличием Сollection от Iterable является то, 
 * что в нём есть возможности:
 *      int size() - узначть количество элементов в контейнере
 *      boolean add(E e) - добавляет элемент, причём будет ли он 
 *      действительно добавлен, зависит от реального класса коллекции:
 *           так для ArrayList элемент будет добавлен в любом случае,
 *           для HashSet - только в том случае, если элемента в коллекции
 *           ранее не было; возвращаемое значение true соответствует ситуации,
 *           при которой коллекция действительно изменилась
 *      boolean remove(Object o) удаляет объект, который по значению (o.equals(e))
 *           равен переданному
 *      void clear() - удаляет все элементы из коллекции
 *      Object[] toArray() - создаёт массив ссылок на объекты коллекции
 * 
 *  Естественно, что такие методы неявно подразумевают, что Collection - это 
 *  конечный набор элементов, а не последовательность!
 */
final class CollectionExample {
    public static void test() {
        /**
         * Collection - это интерфейс, потому мы не можем создавать экземпляры Collection,
         * мы должны создавать экземпляры конечных классов, реализующих этот интерфейс.
         */
        System.out.println("Добавляем три элемента в HashSet");
        Collection<String> set = new HashSet<>();
        System.out.println(set.add("Anna"));
        System.out.println(set.add("Viktor"));
        System.out.println(set.add("Anna")); // так как элемент повторяется, то выводится false
        System.out.println(set.size()); // ожидаем 2

        System.out.println("Добавляем три элемента в ArrayList");
        Collection<String> arr = new ArrayList<>();
        System.out.println(arr.add("Anna"));
        System.out.println(arr.add("Viktor"));
        System.out.println(arr.add("Anna")); // хотя элемент повторяется, ArrayList позволяет повторы - выводится true
        System.out.println(arr.size()); // ожидаем 3
    }
}

public class CollectionIntroduction {
        public static void main(String[] args) {
        if (false) StringStretchIterator.test();
        if (false) StringStretch.test();
        if (false) SyracuseSequence.test();
        if (false) CollectionExample.test();
    }
}
